﻿/*
 *  XTL - XNA Tool Library, a collection with useful XNA classes
 * 
 *  Copyright (C) 2009 Sebastian Schäfer
 *  Copyright (C) 2009 Frank Nagl, www.franknagl.de
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License 
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
namespace XTL.ImageProcessing
{
    using System;
    using System.Drawing;
    using Microsoft.Xna.Framework.Graphics;
    using System.IO;
    using System.Drawing.Imaging;
    using System.Runtime.InteropServices;

    /// <summary>
    /// Helper class for different Image converts. See function details for more information.
    /// </summary>
    public static class ImageConverter
    {
        /// <summary>Represents the separate channels of a RGB image.</summary>
        public struct RGB
        {
            /// <summary>Color channel <c>blue</c>.</summary>
            public const short B = 0;
            /// <summary>Color channel <c>green</c>.</summary>
            public const short G = 1;
            /// <summary>Color channel <c>red</c>.</summary>
            public const short R = 2;
        }

        /// <summary>Represents the separate channels of an ARGB image.</summary>
        public struct ARGB
        {
            /// <summary>Color channel <c>blue</c>.</summary>
            public const short B = 0;
            /// <summary>Color channel <c>green</c>.</summary>
            public const short G = 1;
            /// <summary>Color channel <c>red</c>.</summary>
            public const short R = 2;
            /// <summary>Transparence channel <c>alpha</c>.</summary>
            public const short A = 3;
        }

        /// <summary>
        /// Converts Texture2D to 32 bit Bitmap.
        /// </summary>
        /// <param name="texture">The texture object, which should be converted.</param>
        /// <returns>The created Bitmap image.</returns>
        public static Bitmap TextureToARGB(Texture2D texture)
        {
            byte[] textureData = new byte[4 * texture.Width * texture.Height];
            texture.GetData<byte>(textureData);

            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(
                           texture.Width, texture.Height,
                           System.Drawing.Imaging.PixelFormat.Format32bppArgb
                         );

            System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(
                           new System.Drawing.Rectangle(0, 0, texture.Width, texture.Height),
                           System.Drawing.Imaging.ImageLockMode.WriteOnly,
                           System.Drawing.Imaging.PixelFormat.Format32bppArgb
                         );

            // Get the address of the first line. 
            IntPtr safePtr = bmpData.Scan0;
            System.Runtime.InteropServices.Marshal.Copy
                (textureData, 0, safePtr, textureData.Length);

            //Bitmap width * 4 (for ARGB per pixel)
            int stride = bmpData.Stride;
            int arraySize = stride * bmpData.Height;
            // Declare an array to hold the bytes of the bitmap
            byte[] values = new byte[arraySize];

            // Copy the values into the array.
            System.Runtime.InteropServices.Marshal.Copy(safePtr, values, 0, arraySize);

            //Sets the alpha channel to 255, otherwise it would always be 0.
            for (int i = 0; i < arraySize; i += 4)
                values[i + ARGB.A] = 255;

            // Copy the ARGB values back to the bitmap data
            System.Runtime.InteropServices.Marshal.Copy(values, 0, safePtr, arraySize);

            // unlock image
            bitmap.UnlockBits(bmpData);
            return bitmap;
        }

        /// <summary>
        /// Converts Texture2D to a 24bit Bitmap.
        /// </summary>
        /// <param name="texture">The texture Object, that should be converted.</param>
        /// <returns>Bitmap image</returns>
        public static Bitmap TextureToRGB(Texture2D texture)
        {
            byte[] texData = new byte[4 * texture.Width * texture.Height];
            texture.GetData<byte>(texData);
            // offset in rgb images
            int offset = (3 * texture.Width) % 4;
            if (offset != 0)
                offset = 4 - offset;
            byte[] texDataRGB = new byte[(3 * texture.Width + offset) * texture.Height];
            int nTex = 0; //position in texture array
            int nRgb = 0; //position in rgb array

            // for each line
            for (int y = 0; y < texture.Height; y++)
            {
                // for each pixel
                for (int x = 0; x < (4 * texture.Width); x++)
                {
                    // ignore alpha channel
                    if ((nTex + 1) % 4 == 0)
                    {
                        nTex++;
                        continue;
                    }
                    texDataRGB[nRgb] = texData[nTex];
                    nRgb++;
                    nTex++;
                }
                nRgb += offset;
            }

            Bitmap bitmap = new Bitmap(texture.Width, texture.Height, PixelFormat.Format24bppRgb);

            BitmapData bmpData = bitmap.LockBits(
                           new System.Drawing.Rectangle(0, 0, texture.Width, texture.Height),
                           ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            // Get the address of the first line. 
            IntPtr safePtr = bmpData.Scan0;
            Marshal.Copy(texDataRGB, 0, safePtr, texDataRGB.Length);

            // unlock image
            bitmap.UnlockBits(bmpData);
            return bitmap;
        }

        /// <summary>
        /// Converts a pixel color array into a bitmap. The format of the byte array is treated
        /// as 32 bit pair RGBA order, so the array needs to have exactly width * height * 4 elements.
        /// </summary>
        /// <param name="imageData">Pixel color array</param>
        /// <param name="width">Image width in pixel.</param>
        /// <param name="height">Image height in pixel.</param>
        /// <returns></returns>
        public static Bitmap ImageDataToBitmap(byte[] imageData, int width, int height)
        {
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(
                           width, height,
                           System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(
                           new System.Drawing.Rectangle(0, 0, width, height),
                           System.Drawing.Imaging.ImageLockMode.WriteOnly,
                           System.Drawing.Imaging.PixelFormat.Format32bppArgb
                         );
            // Get the address of the first line. 
            IntPtr safePtr = bmpData.Scan0;
            System.Runtime.InteropServices.Marshal.Copy
                (imageData, 0, safePtr, imageData.Length);

            //Bitmap width * 4 (for ARGB per pixel)
            int stride = bmpData.Stride;
            int arraySize = stride * bmpData.Height;
            // Declare an array to hold the bytes of the bitmap
            byte[] values = new byte[arraySize];

            // Copy the values into the array.
            System.Runtime.InteropServices.Marshal.Copy(safePtr, values, 0, arraySize);
            //Sets the alpha channel to 255, otherwise it would always be 0.
            for (int i = 0; i < arraySize; i += 4)
                values[i + ARGB.A] = 255;
            // Copy the ARGB values back to the bitmap data
            System.Runtime.InteropServices.Marshal.Copy(values, 0, safePtr, arraySize);
            // unlock image
            bitmap.UnlockBits(bmpData);
            return bitmap;
        }


        /// <summary>
        /// Converts Bitmap to Texture2D.
        /// </summary>
        /// <param name="bitmap">The Bitmap object, which should be converted.</param>
        /// <param name="graphicsDevice">The XNA graphics device.</param>
        /// <returns>The created Texture2D object.</returns>
        public static Texture2D BitmapToTexture(Bitmap bitmap, GraphicsDevice graphicsDevice)
        {
            Bitmap b = bitmap;
            Texture2D texture = null;
            using (MemoryStream s = new MemoryStream())
            {
                b.Save(s, ImageFormat.Bmp);
                s.Seek(0, SeekOrigin.Begin); //Do this, otherwise error is thrown in next line
                texture = Texture2D.FromFile(graphicsDevice, s);
            }
            return texture;
        }
    }
}
